--[[
Copyright (c) 2021 <roberto.vpt@protonmail.com> . All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]]--

--[[
**2021-11-26 Roberto Soccoli

# RENDER GEMLINE ver 1

Dopo una mia proposta iniziale, che non era allineata con gli usi della
comunità Gemini che principalmento usano interfacce testuali, ho cercato di
implementare le mie idee assecondando l'uso comune.
Il corretto uso dei tag è devoluto allo scrivente.

> Questa versione è predisposta per essere usata nel client DEMO di
> solderpunk senza sostanziali modifiche.

## I TAG DI EVIDENZIAZIONE BOLD E ITALIC

* I tag sono * per *BOLD* e " per "ITALIC".
* I tag sono riconosciuti solo a inizio o fine parola, ** altrimenti sono testo.
* I tag isolati *sono mostrati*.
* Se ci sono entrambi "*bold non viene mostrato*".
* I tag raddoppiati, ""**non sono mostrati**"".

## VALORI TAG

0 SPAZIO
1 BOLD mostrato *
2 ITALIC mostrato "
3 entrambi, mostrato solo "
4 NULL
5 bold nascosto
6 italic nascosto
7 entrambi nascosti

2021-11-29
Il formattare in un testo da scrivere direttamente su terminale non permette una buona paginazione.
Gemline è meglio che torni la tabella rows ed il livello superiore farà quanto deve.
Peraltro la paginazione deve essere semplice, con testo abbastanza corto e le linee complete. 
Le pagine vanno calcolate sul numero di righe sommate per linea.

]]--   

-- danno fastidio se li metto anche qui??
preformatted = false
links = {}

socket = require("socket")
socket.url = require("socket.url")

function render ( line, margin )
   -- A CAPO GIUSTIFICATO, BOLD E ITALIC
   margin = margin or 74   -- 2/3=51, 1/2=38 e 1/3=27
   -- indentazione a 4 caratteri per supportare più di 9 link
   -- per =>_ che diventa _1>_ o 10>_ 
   local isIndent = false
   local indent = "    "
   local start = 1
   local len = 0
   local tags = true      -- not for #, ## and ###
   local title = false   -- onli #
   local words = {}      -- first step
   local rows = {}      -- line rows
   local row = {}
   local url            
   
   function skip()
      if line and start < #line then
         while string.byte( line, start ) < 33 do
            if start == #line then break end
            start = start + 1
         end
      end
   end
   
   function toWords ( text )
      text = text or ""
      local p = 1
      while p <= #text do
         local i, f = string.find( text, "%s+", p )
         if i then
            if i > p then
               local temp = string.sub( text, p, i - 1 )
               if #temp == 1 and string.find( temp, "^%p+" ) then
                  words[#words+1] = 4
               end
               words[#words+1] = temp
            end
            words[#words+1] = 0
            p = f + 1
         else
            local temp = string.sub( text, p )
            if #temp == 1 and string.find( temp, "^%p+" ) then
               words[#words+1] = 4
            end
            words[#words+1] = temp
            break
         end
      end
   end
   
   function switch ( pos  )
      if pos == 1 or words[pos-1] == 0 then -- apertura
         if (words[pos] & 2) == 2 then
            if words[pos] < 4 then row[#row+1] = '“' end
            row[#row+1] = "\27[3m"
         end
         if (words[pos] & 1) == 1 then
            row[#row+1] = "\27[1m" 
            if words[pos] == 1 then row[#row+1] = '*' end
         end
      else
         if (words[pos] & 1) == 1 then
            if words[pos] == 1 then row[#row+1] = '*' end
            row[#row+1] = "\27[22m"
         end
         if (words[pos] & 2) == 2 then
            row[#row+1] = "\27[23m"
            if words[pos] < 4 then row[#row+1] = '”' end
         end
      end
   end
   -- MAIN
   if line and #line > 0 then
      if string.sub(line,1,3) == "```" then
         preformatted = not preformatted
      elseif preformatted then
         return line .. "\n"
      elseif string.sub( line, 1, 1 ) == ">" then
         indent = "  ┃ "
         row[#row+1] = indent
         start = 2
      elseif string.sub( line, 1, 2 ) == "=>" then
         start = 3
         while string.byte( line, start ) < 33 do start = start + 1 end
         local i, f = string.find( line, "%s+", start )
         local link
         if i then
            link = string.sub( line, start, i - 1 )
            start = f + 1
         else
            link = string.sub( line, start )
         end
         table.insert(links, socket.url.absolute(url, link))
         local temp = string.sub( " "..#links, -2 )
         row[#row+1] = "\27[34m"..temp..">\27[39m "
      elseif string.sub( line, 1, 2 ) == "* " then
         row[#row+1] = "  \27[1m•\27[m "
         start = 3
      elseif string.sub( line, 1, 1 ) == "#" then
         indent = ""
         tags = false
         if string.sub( line, 1, 3 ) == "###" then
            row[#row+1] = "\27[1m\27[4m"
            start = 4
         elseif string.sub( line, 1, 2 ) == "##" then
            row[#row+1] ="\27[1m\27[21m"
            start = 3
         elseif string.sub( line, 1, 1 ) == "#" then
            indent = "    "
            row[#row+1] = indent.."\27[1m"
            title = true
            start = 2
         end
      else
         indent = ""
      end
      -- LINE INIT
      if start > 1 then
         skip()
      else
         skip()
         if start > 1 then
            indent = "    "
            row[#row+1] = indent
         end
      end
      if #indent > 0 then
         isIndent = true
         len = 4
      end
      -- TEXT LOAD
      -- first round: line to words, spaces and text tags
      while start <= #line do
         local pos
         if tags then
            pos, f = string.find( line, '["%*]+', start )
         end
         if pos then
            if pos > start then
               toWords( string.sub( line, start, pos - 1 ) )
            end      
            words[#words+1] = string.sub( line, pos, f )
            start = f + 1
         else
            toWords( string.sub( line, start ) ) 
            break
         end
      end
      -- second round: find and compile text tags
      for n = 1, #words do
         if type( words[n] ) == "string" then
            local tag = string.sub( words[n], 1, 1 ) 
            if ( tag == '*' or tag == '"' ) and
               -- una tag è circondato almeno da uno spazio
               (
                  n == 1 or n == #words or
                   type( words[n-1] ) ~= type( words[n+1] )
               )
            then
               local temp = words[n]
               --print( temp )
               local code = 0
               for t = 1, #temp do
                  tag = string.sub( temp, t, t )
                  if tag == '*' then
                     if ( code & 1 ) == 1 then      -- bold
                        code = ( code & 3 ) + 4      -- nascosto
                     else
                        code = ( code & 6 ) + 1      -- set bold
                     end
                  elseif tag == '"' then
                     if ( code & 2 ) == 2 then      -- italic
                        code = ( code & 3 ) + 4      -- nascosto
                     else
                        code = ( code & 5 ) + 2      -- set italic 
                     end
                  end                     
               end -- for
               words[n] = code
            end
         end -- solo stringhe, salta gli spazi
      end -- end for

      --   ROW FORMAT
      local head = 1
      local tail = 1
      local spaces = 0
      for n = 1, #words do
         if type( words[ n ] ) == "string" then
            if len + #words[n] > margin then
               local riporto = words[n]
               local rip = #riporto
               pos = n+1 -- salva posizione successiva
               local trat = string.find( riporto, '-', 1, true )
               if trat and trat <= margin - len then
                  -- divide la parola al trattino
                  riporto = string.sub( words[n], trat + 1 )
                  rip = #riporto
                  words[n] = string.sub( words[n], 1, trat )
                  len = len + trat
                  tail = n
               else
                  -- riporto a capo 
                  tail = n - 1
                  -- elimina fino a spazio finale
                  if words[tail] ~= 0 then -- sono tags
                     if words[tail] & 1 == 1 then
                        riporto = "\27[1m"..riporto
                     end
                     if words[tail] < 4 then
                        len = len - 1
                        rip = rip + 1
                        if words[tail] == 1 then
                           riporto = "*"..riporto
                        end
                     end
                     if words[tail] & 2 == 2 then
                        riporto = "\27[3m"..riporto
                        if words[tail] < 4 then
                           riporto = "“"..riporto
                        end
                     end
                     tail = tail - 1
                  end
                  -- lo spazio viene saltato
                  spaces = spaces - 1
                  len = len - 1
                  tail = tail - 1
               end
               -- giustifica la riga
               local a = margin - len
               local t = a + spaces
               local x = t / spaces
               local s = 0
               for m = head, tail do
                  if type( words[m] ) == "string" then
                     row[#row+1] = words[m]
                  else
                     if words[m] == 0 then
                        spaces = spaces - 1
                        s = s + x
                        local temp = math.floor( s + 0.5 )
                        t=t-temp
                        if spaces == 1 then temp = t end
                        row[#row+1] = string.rep( " ", temp )
                        s = s - temp
                     else
                        switch ( m )
                     end
                  end
               end 
               -- si va a capo
               row[#row+1] = "\n"   -- CHIUSURA
               rows[#rows+1] = table.concat( row )
               -- nuova row
               row = {}
               row[1] = indent..riporto
               riporto = ""
               len = rip 
               if isIndent then len = len + 4 end
               spaces = 0
               -- PRONTI AD AVANZARE
               head = pos -- OK
            else
               len = len + #words[n]
            end      
         else -- SPACE AND TAGS
            if words[n] < 4 then len = len + 1 end
            if words[n] == 0 then spaces = spaces + 1 end
         end
      end   -- fine scansione 
      -- parte non giustificata
      for m = head, #words do
         if type( words[m] ) == "string" then
            row[#row+1] = words[m]
         else
            if words[m] == 0 then
               row[#row+1] = " "
            else
               switch( m )
            end
         end
      end
      row = table.concat( row ) -- compreso margine
      if title then   -- centrato
         local s = math.tointeger( math.floor(( margin - #row + 0.5 ) / 2 ) ) 
         row = string.rep( " ", s )..row
      end
      rows[#rows+1] = row
      -- CHIUSURA ULTIMA RIGA         
      rows[#rows+1] = "\27[m\n"
    else
      rows[#rows+1] = "\n"
    end
   return rows
end

return render

   
   

